package com.gitee.wsl.base

import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

sealed interface Or<out L, out R> {
    class One<out L>(val value :L):Or<L, Nothing>{
        companion object {
           @PublishedApi
           internal val unit: Or<Unit, Nothing> = One(Unit)
       }
   }

    class Two<out R>(val value :R):Or<Nothing,R>{
        companion object {
            @PublishedApi
            internal val unit: Or<Nothing, Unit> = Two(Unit)
        }
    }
}

/**
 * Applies `fa` if this is a [Or.One], `fb` if this is a [Or.Two]
 *
 *
 * @param fa the function to apply if this is a [Or.One]
 * @param fb the function to apply if this is a [Or.Two]
 * @return the results of applying the function
 */
@OptIn(ExperimentalContracts::class)
inline fun <L, R, C> Or<L, R>.fold(fa: (L) -> C, fb: (R) -> C): C {
    contract {
        callsInPlace(fa, InvocationKind.AT_MOST_ONCE)
        callsInPlace(fb, InvocationKind.AT_MOST_ONCE)
    }
    return when (this) {
        is Or.One -> fa(value)
        is Or.Two -> fb(value)
    }
}

val Or<*,*>.isOne
    get() = this is Or.One

val Or<*,*>.isTwo
    get() = this is Or.Two


val <L,R> Or<L,R>.one :L?
    get() = if(this is Or.One) this.value else null

val <L,R> Or<L,R>.two :R?
    get() = if(this is Or.Two) this.value else null


infix fun <L,R:Any> L.Or(other:R):Or<L,R> = if(this == null) Or.Two(other) else Or.One(this)

fun <L,R> Or<L,R>.one(block:(L)->Unit):Or<L,R> = apply {
    if(this is Or.One) block(this.value)
}

fun <L,R> Or<L,R>.two(block:(R)->Unit):Or<L,R> = apply {
    if(this is Or.Two) block(this.value)
}

sealed interface Or3<out L, out R,out T>{
    class One<out L>(val value :L):Or3<L,Nothing, Nothing>

    class Two<out R>(val value :R):Or3<Nothing,R, Nothing>

    class Three<out T>(val value :T):Or3<Nothing, Nothing,T>
}

val Or3<*,*,*>.isOne
    get() = this is Or3.One

val Or3<*,*,*>.isTwo
    get() = this is Or3.Two

val Or3<*,*,*>.isThree
    get() = this is Or3.Three

val <L,R,T> Or3<L,R,T>.one :L?
    get() = if(this is Or3.One) this.value else null

val <L,R,T> Or3<L,R,T>.two :R?
    get() = if(this is Or3.Two) this.value else null

val <L,R,T> Or3<L,R,T>.three :T?
    get() = if(this is Or3.Three) this.value else null

fun <L,R,T> Or3<L,R,T>.one(block:(L)->Unit):Or3<L,R,T> = apply {
    if(this is Or3.One) block(this.value)
}

fun <L,R,T> Or3<L,R,T>.two(block:(R)->Unit):Or3<L,R,T> = apply {
    if(this is Or3.Two) block(this.value)
}

fun <L,R,T> Or3<L,R,T>.three(block:(T)->Unit):Or3<L,R,T> = apply {
    if(this is Or3.Three) block(this.value)
}

sealed interface Or4<out L, out R,out T,out K>{
    class One<out L>(val value :L):Or4<L,Nothing, Nothing, Nothing>

    class Two<out R>(val value :R):Or4<Nothing,R, Nothing, Nothing>

    class Three<out T>(val value :T):Or4<Nothing, Nothing,T, Nothing>

    class Four<out K>(val value :K):Or4<Nothing, Nothing, Nothing,K>
}

val Or4<*,*,*,*>.isOne
    get() = this is Or4.One

val Or4<*,*,*,*>.isTwo
    get() = this is Or4.Two

val Or4<*,*,*,*>.isThree
    get() = this is Or4.Three

val Or4<*,*,*,*>.isFour
    get() = this is Or4.Four

fun <L,R,T,K> Or4<L,R,T,K>.one(block:(L)->Unit):Or4<L,R,T,K> = apply {
    if(this is Or4.One) block(this.value)
}

fun <L,R,T,K> Or4<L,R,T,K>.two(block:(R)->Unit):Or4<L,R,T,K> = apply {
    if(this is Or4.Two) block(this.value)
}

fun <L,R,T,K> Or4<L,R,T,K>.three(block:(T)->Unit):Or4<L,R,T,K> = apply {
    if(this is Or4.Three) block(this.value)
}

fun <L,R,T,K> Or4<L,R,T,K>.four(block:(K)->Unit):Or4<L,R,T,K> = apply {
    if(this is Or4.Four) block(this.value)
}